//
//  session.h
//  diylane-api
//
//  Created by Yucheng on 5/17/17.
//  Copyright © 2017 Yucheng. All rights reserved.
//

#ifndef __middlewares_session_hpp
#define __middlewares_session_hpp

#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/pointer.h>

#include "modules/loggger/logger.hpp"
#include "modules/server.hpp"
#include "modules/redis.hpp"

#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/thread.hpp>

namespace diylane
{
namespace http
{

static const char *Middleware_Session = "session";

struct HttpSessionConfig
{
    std::string name = "sid";
    std::string prefix = "1";
    uint32_t max_age = 60 * 5; // seconds
};

struct HttpSessionMiddleware;

struct HttpSession : util::Autodelete
{
    HttpSessionMiddleware *middleware = NULL;
    HttpSessionConfig *config = NULL;
    std::string sid = "";
    bool create_new = false;
    rapidjson::Document data;

    HttpSession(HttpSessionMiddleware *_middleware, HttpSessionConfig *_config)
    {
        middleware = _middleware;
        config = _config;
    }

    ~HttpSession()
    {
    }

    bool empty()
    {
        if (sid.length())
            return false;
        return true;
    }

    // create new session
    void createNew()
    {
        boost::uuids::uuid uid = rgen();
        sid = config->prefix;
        sid += "-";
        sid += boost::lexical_cast<std::string>(uid);
        create_new = true;
    }

  private:
    boost::uuids::random_generator rgen;
};

struct HttpSessionMiddleware : HttpMiddleware
{
    HttpSessionConfig config;
    redis::Pool pool;

    HttpSessionMiddleware()
    {
    }

    ~HttpSessionMiddleware()
    {
    }

    bool before(HttpRequest *request, HttpResponse *response)
    {
        HttpSession *ctx = new HttpSession(this, &config);
        request->setContext(Middleware_Session, ctx);

        const char *sessionCookie = request->getCookie(config.name.c_str());
        if (sessionCookie == NULL)
        {
            return true;
        }

        // fetch data from redis server
        redis::Reply reply;
        pool.redisCommand(reply, "GET session-%s", sessionCookie);
        if (reply.unwrap() == NULL)
        {
            LOG_ERROR(Logger::inst()) << "HttpSessionMiddleware::failed, " << reply.error << std::endl;
            return true;
        }
        if (reply.unwrap()->type != REDIS_REPLY_STRING)
        {
            return true;
        }

        // update session info
        if (ctx->data.Parse(reply.unwrap()->str).HasParseError())
        {
            LOG_ERROR(Logger::inst()) << "HttpSessionMiddleware::parse data failed." << std::endl;
            return true;
        }
        ctx->sid = sessionCookie;

        return true;
    }

    bool sync(HttpSession *ctx)
    {

        return true;
    }

    void after(HttpRequest *request, HttpResponse *response)
    {
        util::AutodeleteMarker autoDelete;
        HttpSession *ctx = (HttpSession *)request->getContext(Middleware_Session);
        if (ctx)
        {
            autoDelete.mark(ctx);
            if (ctx->sid.size())
            {

                // dump data
                rapidjson::StringBuffer buffer;
                rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
                ctx->data.Accept(writer);

                // sync with remote redis server
                redis::Reply reply;
                pool.redisCommand(reply, "SET session-%s %s EX %u", ctx->sid.c_str(), buffer.GetString(), config.max_age);
                if (reply.unwrap() == NULL)
                {
                    LOG_ERROR(Logger::inst()) << "HttpSessionMiddleware::failed, " << reply.error << std::endl;
                    return;
                }
                if (reply.unwrap()->type != REDIS_REPLY_STATUS || strcmp(reply.unwrap()->str, "OK") != 0)
                {
                    LOG_ERROR(Logger::inst()) << "HttpSessionMiddleware::failed, " << reply.unwrap()->str << std::endl;
                    return;
                }

                // set cookie
                if (ctx->create_new)
                {
                    http::Cookie *coo;
                    coo = new http::Cookie(config.name.c_str(), ctx->sid.c_str());
                    response->cookieJar.set(coo);
                }
            }
        }
    }
};
}
}

#endif
